/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.contexts;

import bsh.Interpreter;
import cn.easyplatform.EasyPlatformRuntimeException;
import cn.easyplatform.dao.DaoException;
import cn.easyplatform.dos.FieldDo;
import cn.easyplatform.dos.Record;
import cn.easyplatform.entities.beans.list.ListBean;
import cn.easyplatform.entities.beans.table.TableBean;
import cn.easyplatform.entities.beans.task.TaskBean;
import cn.easyplatform.entities.beans.task.Variable;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.messages.vos.ListInitVo;
import cn.easyplatform.messages.vos.TaskVo;
import cn.easyplatform.messages.vos.datalist.ListBatchVo;
import cn.easyplatform.support.scripting.BreakPoint;
import cn.easyplatform.support.scripting.RhinoScriptable;
import cn.easyplatform.type.Constants;
import cn.easyplatform.type.EntityType;
import cn.easyplatform.type.FieldVo;
import cn.easyplatform.type.ScopeType;
import cn.easyplatform.util.RuntimeUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;

/**
 * 一级总流程控制
 *
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class WorkflowContext implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 5281223309236496336L;

    private final static Logger log = LoggerFactory.getLogger(WorkflowContext.class);

    public final static String PREFIX = "GID";// id前缀

    private Map<String, Object> systemVariables;// 系统参统

    private Map<Integer, TaskContext> taskList;// 工作区所属的任务列表

    private int layer;// 当前层

    private TaskContext ctx;// 当前任务信息

    private RecordContext prev;// 上一个任务

    private String id;// 当前工作区标识号

    private String name;// 功能名称

    private BreakPoint breakPoint;// 断点

    private String parentId;// 所属的父标识id

    private transient List<TaskContext> committedTasks;// 保留已提交的功能队列

    private ListBatchVo pendingList;// 选择列表多笔记录进行批量修改

    private RecordContext host;// 主功能记录，有可能是主页面或列表

    private String hostKey;// 从列表打开的功能

    private WorkflowContext() {
    }

    /**
     * 普通的功能
     *
     * @param tb
     * @param tv
     */
    public WorkflowContext(TaskBean tb, TaskVo tv) {
        this(tb, tv.getOpenModel(), (RecordContext) null, null);
        systemVariables.put("806", tv.getRoleId());
        ctx.setParameter("806", tv.getRoleId());
        if (tv.getAgentId() != null) {
            systemVariables.put("807", tv.getAgentId());
            ctx.setParameter("807", tv.getAgentId());
        }
        if (tv.getVariables() != null) {
            for (FieldVo fv : tv.getVariables()) {
                FieldDo fd = new FieldDo(fv.getType());
                fd.setScope(ScopeType.PUBLIC);
                fd.setName(fv.getName());
                fd.setValue(fv.getValue());
                ctx.setVariable(fd);
            }
        }
    }

    public WorkflowContext(TaskBean tb, int openModel, RecordContext host) {
        this(tb, openModel, host, null);
    }

    /**
     * 从页面打开的功能
     *
     * @param tb
     * @param openModel
     * @param host
     */
    public WorkflowContext(TaskBean tb, int openModel, RecordContext host,
                           String parentLockKey) {
        this.id = PREFIX + RandomStringUtils.randomNumeric(18);
        this.name = tb.getName();
        layer = 1;
        systemVariables = new HashMap<String, Object>();
        taskList = new HashMap<Integer, TaskContext>();
        RuntimeUtils.initWorkflow(systemVariables, tb);
        systemVariables.put("800", id);
        systemVariables.put("805", openModel);
        systemVariables.put("759", "Begin");
        ctx = new TaskContext(tb, systemVariables);
        ctx.setParameter("828", 1);
        taskList.put(layer, ctx);
        this.host = host;
        this.prev = host;
        this.hostKey = parentLockKey;
        if (prev != null) {
            // 传递公共变量
            for (Map.Entry<String, FieldDo> entry : prev.userVariables
                    .entrySet()) {
                FieldDo fd = entry.getValue();
                if (fd.getScope() == ScopeType.PUBLIC) {
                    if (fd.getShareModel() == Variable.CLONE_VALUE)
                        ctx.userVariables.put(entry.getKey(), fd.clone());
                    else
                        ctx.userVariables.put(entry.getKey(), fd);
                }
            }
            Object role = prev.getParameter("806");
            systemVariables.put("806", role);
            ctx.setParameter("806", role);
            Object agent = prev.getParameter("807");
            if (agent != null) {
                systemVariables.put("807", agent);
                ctx.setParameter("807", agent);
            }
            systemVariables.put("809", prev.getParameter("801"));
            ctx.setParameter("809", prev.getParameter("801"));
            systemVariables.put("840", prev.getParameter("800"));
            ctx.setParameter("840", prev.getParameter("800"));
        }
        if (log.isDebugEnabled())
            log.debug("create WorkflowContext:id[{}],id[{}] {}", id, tb.getId(), systemVariables);
    }

    /**
     * Detail列表open的功能
     *
     * @param lc
     * @param host
     */
    public WorkflowContext(ListContext lc, int openModel, RecordContext host) {
        this.id = PREFIX + RandomStringUtils.randomNumeric(12);
        this.name = lc.getBean().getName();
        this.host = host;
        host.setParameter("811", name);
        host.setParameter("812", "form.png");
        host.setParameter("805", openModel);
        host.setParameter("759", "Begin");
        ctx = new TaskContext(host);
        taskList = new HashMap<Integer, TaskContext>();
        taskList.put(1, ctx);
        if (log.isDebugEnabled())
            log.debug("create WorkflowContext for datalist:id[{}],name[{}]", id,
                    name);
    }

    /**
     * @return
     */
    public RecordContext createRecord(Record record) {
        return ctx.createRecord(record);
    }

    public boolean next(int from, TaskBean tb) {
        ctx.clear(null, false);
        if (from == layer || from == -1)
            prev = ctx.getRecordContext();
        else if (taskList.containsKey(from))
            prev = taskList.get(from).getRecordContext();
        if (prev != null) {
            layer++;
            String cmd = prev.getParameterAsString("759");
            ctx = new TaskContext(tb, systemVariables);
            ctx.setParameter("759", cmd);
            ctx.setParameter("828", layer);
            ctx.setParameter("829", from);
            // 传递公共变量
            for (Map.Entry<String, FieldDo> entry : prev.userVariables
                    .entrySet()) {
                FieldDo fd = entry.getValue();
                if (fd.getScope() == ScopeType.PUBLIC) {
                    if (fd.getShareModel() == Variable.CLONE_VALUE)
                        ctx.userVariables.put(entry.getKey(), fd.clone());
                    else
                        ctx.userVariables.put(entry.getKey(), fd);
                }
            }
        }
        return prev != null;
    }

    public boolean prev(CommandContext cc) {
        if (layer == 1)
            return false;
        String cmd = ctx.getParameterAsString("759");
        // 删除当前功能
        TaskContext tc = taskList.remove(layer);
        if (tc != null) {
            tc.clear(cc, true);
            tc = null;
        }
        layer--;
        while (layer > 0) {
            tc = taskList.get(layer);
            String type = tc.getParameterAsString("830");
            if (tc.getParameterAsBoolean("816")
                    && (type.equals(EntityType.PAGE.getName()) || type
                    .equals(EntityType.DATALIST.getName()))) {
                break;
            } else {
                taskList.remove(layer);
                tc.clear(cc, true);
                tc = null;
                layer--;
            }
        }
        if (layer == 0)
            return false;
        cc.getSync().unlock(id, layer);
        ctx = null;
        ctx = taskList.get(layer);
        ctx.setParameter("759", cmd);
        String type = ctx.getParameterAsString("830");
        return ctx.getParameterAsBoolean("816")
                && (type.equals(EntityType.PAGE.getName()) || type
                .equals(EntityType.DATALIST.getName()));
    }

    public void store() {
        taskList.put(layer, ctx);
    }

    public boolean isAutoMapping() {
        if (prev == null)
            return false;
        if (Strings.isBlank(prev.getParameterAsString("833"))
                || Strings.isBlank(ctx.getParameterAsString("833")))
            return false;
        String type = prev.getParameterAsString("830");
        return (type.equals(EntityType.PAGE.getName()) || type
                .equals(EntityType.DATALIST.getName()))
                && prev.getParameterAsString("833").equals(
                ctx.getParameterAsString("833"));
    }

    public void autoMapping(CommandContext cc) {
        if (prev != null && prev.getData() != null)
            ctx.getRecordContext().setData(prev.getData().clone());
    }

    public RecordContext[] getMappingRecord() {
        RecordContext[] rcs = null;
        if (prev != null) {
            rcs = new RecordContext[2];
            rcs[0] = prev;
            rcs[1] = ctx.getRecordContext();
        } else {
            rcs = new RecordContext[1];
            rcs[0] = ctx.getRecordContext();
        }
        return rcs;
    }

    public void reset(CommandContext cc) {
        cc.getSync().unlock(id, 0);
        for (int index = layer; index > 1; index--) {
            TaskContext tc = taskList.remove(index);
            tc.clear(cc, true);
            tc = null;
            if (log.isDebugEnabled())
                log.debug("reset->{}", index);
        }
        prev = null;
        ctx = taskList.get(1);
        layer = 1;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    /*
     * 功能之间的关系
     */
    public void appendChild(CommandContext cc, String cid, WorkflowContext child) {
        ctx.appendChild(cc, cid, child);
        child.parentId = id;
    }

    public void removeChild(WorkflowContext child) {
        for (TaskContext tc : taskList.values()) {
            if (tc.removeChild(child)) {
                child.parentId = null;
                break;
            }
        }
    }

    public void removeAll(CommandContext cc) {
        if (hostKey != null)
            cc.getProjectService().getRowLockProvider(cc).unlock(hostKey);
        if (taskList == null)
            return;
        for (TaskContext tc : taskList.values())
            tc.clear(cc, true);
    }

    public String getParentId() {
        return parentId;
    }

    public Set<Map.Entry<String, String>> getChildren() {
        return ctx.getChildren();
    }

    /*
     * 数据列表操作
     */

    /**
     * 从指定的层获取数据列表
     *
     * @param layer
     * @param id
     * @return
     */
    public ListContext getList(int layer, String id) {
        if (layer == 0)
            return ctx.getList(id);
        else if (layer < this.layer)
            return taskList.get(layer).getList(id);
        return null;
    }

    /**
     * 从指定的层获取<taskbox>子任务信息
     *
     * @param layer
     * @param cid
     * @return
     */
    public String getTask(int layer, String cid) {
        if (layer == 0)
            return ctx.getChild(id);
        else if (layer < this.layer)
            return taskList.get(layer).getChild(cid);
        return null;
    }

    /**
     * @param cid
     * @return
     */
    public String getTask(String cid) {
        return ctx.getChild(cid);
    }

    public ListContext getList(String id) {
        return ctx.getList(id);
    }

    public ListContext setList(CommandContext cc, ListInitVo iv, ListBean bean) {
        return ctx.setList(cc, this, iv, bean);
    }

    public void setList(ListContext lc) {
        ctx.setList(lc);
    }

    public SwiftContext setSwift(String id, TableBean table, boolean isEditable) {
        return ctx.setSwift(id, table, isEditable);
    }

    public SwiftContext getSwift(String id) {
        return ctx.getSwift(id);
    }

    public SwiftContext getSwift(int layer, String id) {
        if (layer == 0)
            return ctx.getSwift(id);
        else if (layer < this.layer)
            return taskList.get(layer).getSwift(id);
        return null;
    }

    public void removeSwift(String id) {
        ctx.removeSwift(id);
    }

    public List<SwiftContext> getSwifts() {
        return ctx.getSwifts();
    }

    public void removeList(String id) {
        ctx.removeList(id);
    }

    public List<ListContext> getLists() {
        return ctx.getLists();
    }

    public List<ListContext> getLists(int layer) {
        if (layer == 0)
            return ctx.getLists();
        if (layer < this.layer)
            return taskList.get(layer).getLists();
        return null;
    }

    // /////////////////END///////////////////////

    /**
     * @param id
     */
    public ReportContext setReport(String id, String entityId, String ids,
                                   char state, String table) {
        return ctx.setReport(id, entityId, ids, state, table);
    }

    public ReportContext getReport(String id) {
        return ctx.getReport(id);
    }

    /**
     * @return
     */
    public Collection<ReportContext> getReports() {
        return ctx.getReports();
    }

    // /////////////主页面的记录///////////////////
    public RecordContext getRecordContext(int layer) {
        if (layer == 0)
            return ctx.getRecordContext();
        if (layer < this.layer)
            return taskList.get(layer).getRecordContext();
        return null;
    }

    public RecordContext getRecord() {
        return ctx.getRecordContext();
    }

    public void setRecord(RecordContext rc) {
        ctx.setRecordContext(rc);
    }

    public WorkflowContext setData(Record data) {
        ctx.getRecordContext().setData(data);
        return this;
    }

    public Record getData() {
        return ctx.getRecordContext().getData();
    }

    // /////////////////END///////////////////////

    /**
     * @param name
     * @param value
     * @return
     */
    public WorkflowContext setParameter(String name, Object value) {
        ctx.setParameter(name, value);
        return this;
    }

    /**
     * @param name
     * @param value
     */
    public void applyAll(CommandContext cc, String name, Object value) {
        for (TaskContext tc : taskList.values())
            tc.applyAll(cc, name, value);
    }

    /**
     * @param name
     * @return
     */
    public Object getParameter(String name) {
        return ctx.getParameter(name);
    }

    /**
     * @param name
     * @return
     */
    public String getParameterAsString(String name) {
        return ctx.getParameterAsString(name);
    }

    /**
     * @param name
     * @return
     */
    public boolean getParameterAsBoolean(String name) {
        return ctx.getParameterAsBoolean(name);
    }

    /**
     * @param name
     * @return
     */
    public char getParameterAsChar(String name) {
        return ctx.getParameterAsChar(name);
    }

    /**
     * @param name
     * @return
     */
    public int getParameterAsInt(String name) {
        return ctx.getParameterAsInt(name);
    }

    /**
     * @param type
     * @param name
     * @return
     */
    public <T> T getAs(Class<T> type, String name) {
        return ctx.getAs(type, name, null);
    }

    /**
     * @param type
     * @param name
     * @param dft
     * @return
     */
    public <T> T getAs(Class<T> type, String name, T dft) {
        return ctx.getAs(type, name, dft);
    }

    /**
     * 重置当前页面
     */
    public void resetPage(CommandContext cc) {
        ctx.reset(cc);
    }

    /**
     * 执行列表、SWIFT电文确认逻辑，
     */
    public void onNext(CommandContext cc) {
        //ctx.next(cc);
    }

    /**
     * 提交数据
     */
    public void commit(CommandContext cc) {
        if (committedTasks == null)
            committedTasks = new ArrayList<TaskContext>();
        else
            committedTasks.clear();
        // 第1阶段提交
        //if (ctx.getParameterAsInt("805") != Constants.OPEN_EMBBED) {
        try {
            cc.beginTx();
            onPreCommit(cc);
            cc.commitTx();
        } catch (Exception ex) {
            cc.rollbackTx();
            onPreCommitRollback(cc);
            if (ex instanceof EasyPlatformRuntimeException)
                throw (EasyPlatformRuntimeException) ex;
            throw new DaoException("dao.access.transaction.perform", ex,
                    name);
        } finally {
            cc.closeTx();
        }
        committedTasks.clear();
        //}
        // 第2阶段提交,主功能数据及列表等相关页面记录
        TaskContext tmp = ctx;
        try {
            cc.beginTx();
            for (TaskContext tc : taskList.values()) {
                committedTasks.add(tc);
                ctx = tc;
                tc.onBeforeCommit(cc);
            }
            committedTasks.clear();
            for (TaskContext tc : taskList.values()) {
                committedTasks.add(tc);
                ctx = tc;
                tc.commit(cc);
            }
            committedTasks.clear();
            for (TaskContext tc : taskList.values()) {
                committedTasks.add(tc);
                ctx = tc;
                tc.onAfterCommit(cc);
            }
            cc.commitTx();
        } catch (Exception ex) {
            cc.rollbackTx();
            onRollback(cc);
            if (ex instanceof EasyPlatformRuntimeException)
                throw (EasyPlatformRuntimeException) ex;
            throw new DaoException("dao.access.transaction.perform", ex, name);
        } finally {
            cc.closeTx();
            ctx = tmp;
        }
        committedTasks.clear();
        // 第3阶段提交
        //if (ctx.getParameterAsInt("805") != Constants.OPEN_EMBBED) {
        try {
            cc.beginTx();
            onCommitted(cc);
            cc.commitTx();
        } catch (Exception ex) {
            cc.rollbackTx();
            onCommittedRollback(cc);
            if (ex instanceof EasyPlatformRuntimeException)
                throw (EasyPlatformRuntimeException) ex;
            throw new DaoException("dao.access.transaction.perform", ex,
                    name);
        } finally {
            cc.closeTx();
        }
        //}
    }

    private void onRollback(CommandContext cc) {
        if (committedTasks != null) {
            for (TaskContext tc : committedTasks) {
                ctx = tc;
                // 执行回滚逻辑
                tc.onRollback(cc);
                // 子功能
                for (Entry<String, String> entry : tc.getChildren()) {
                    WorkflowContext wc = cc
                            .getWorkflowContext(entry.getValue());
                    wc.onRollback(cc);
                }
            }
        }
    }

    private void onPreCommit(CommandContext cc) {
        if (committedTasks == null)
            committedTasks = new ArrayList<TaskContext>();
        else
            committedTasks.clear();
        TaskContext tmp = ctx;
        try {
            for (TaskContext tc : taskList.values()) {
                // 执行提交成功后逻辑
                committedTasks.add(tc);
                ctx = tc;
                tc.onPreCommit(cc);
                // 子功能
                for (Entry<String, String> entry : tc.getChildren()) {
                    WorkflowContext wc = cc
                            .getWorkflowContext(entry.getValue());
                    wc.onPreCommit(cc);
                }
            }
        } finally {
            ctx = tmp;
        }
    }

    private void onPreCommitRollback(CommandContext cc) {
        if (committedTasks != null) {
            TaskContext tmp = ctx;
            try {
                for (TaskContext tc : committedTasks) {
                    // 执行回滚逻辑
                    ctx = tc;
                    tc.onPreCommitRollback(cc);
                    // 子功能
                    for (Entry<String, String> entry : tc.getChildren()) {
                        WorkflowContext wc = cc.getWorkflowContext(entry
                                .getValue());
                        wc.onPreCommitRollback(cc);
                    }
                }
            } finally {
                ctx = tmp;
            }
        }
    }

    private void onCommitted(CommandContext cc) {
        if (committedTasks == null)
            committedTasks = new ArrayList<TaskContext>();
        else
            committedTasks.clear();
        TaskContext tmp = ctx;
        try {
            for (TaskContext tc : taskList.values()) {
                // 执行提交成功后逻辑
                committedTasks.add(tc);
                ctx = tc;
                tc.onCommitted(cc);
                // 子功能
                for (Entry<String, String> entry : tc.getChildren()) {
                    WorkflowContext wc = cc
                            .getWorkflowContext(entry.getValue());
                    wc.onCommitted(cc);
                }
            }
        } finally {
            ctx = tmp;
        }
    }

    private void onCommittedRollback(CommandContext cc) {
        if (committedTasks != null) {
            TaskContext tmp = ctx;
            try {
                for (TaskContext tc : committedTasks) {
                    // 执行回滚逻辑
                    ctx = tc;
                    tc.onCommittedRollback(cc);
                    // 子功能
                    for (Entry<String, String> entry : tc.getChildren()) {
                        WorkflowContext wc = cc.getWorkflowContext(entry
                                .getValue());
                        wc.onCommittedRollback(cc);
                    }
                }
            } finally {
                ctx = tmp;
            }
        }
    }

    @Override
    public String toString() {
        return "WorkflowContext->" + id;
    }

    /**
     * @return
     */
    public BreakPoint getBreakPoint() {
        return breakPoint;
    }

    /**
     * @param breakPoint
     */
    public void setBreakPoint(BreakPoint breakPoint) {
        // 清除引用
        if (breakPoint == null && this.breakPoint != null) {
            Object ns = this.breakPoint.getNameSpace();
            if (ns instanceof RhinoScriptable) {
                RhinoScriptable scope = (RhinoScriptable) ns;
                scope.clear();
                scope = null;
            } else {
                Interpreter scope = (Interpreter) ns;
                if (scope.getNameSpace() != null)
                    scope.getNameSpace().clear();
                scope.setNameSpace(null);
                scope = null;
            }
        }
        this.breakPoint = breakPoint;
    }

    public ListBatchVo getPendingList() {
        return pendingList;
    }

    public void setPendingList(ListBatchVo pendingList) {
        this.pendingList = pendingList;
    }

    public RecordContext getHost() {
        return host;
    }

    public WorkflowContext clone() {
        WorkflowContext clone = new WorkflowContext();
        clone.id = PREFIX + RandomStringUtils.randomNumeric(18);
        clone.ctx = this.ctx.clone();
        clone.name = this.name;
        clone.systemVariables = new HashMap<String, Object>(this.systemVariables);
        clone.taskList = this.taskList;
        clone.layer = this.layer;
        clone.prev = this.prev;
        clone.parentId = this.parentId;
        clone.hostKey = this.hostKey;
        clone.host = this.host;
        return clone;
    }
}
